package com.brotherly.framework.http;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPInputStream;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.methods.HttpTrace;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;

import android.util.SparseArray;

import com.brotherly.framework.app.BaseApplication;
import com.brotherly.framework.event.EventService;
import com.brotherly.framework.log.LogService;
import com.brotherly.framework.service.IService;
import com.brotherly.framework.service.ServiceHost;
import com.brotherly.framework.transaction.TransactionServiceHost;
import com.brotherly.mpgp.remoteinterface.User;
import com.brotherly.util.FileSystem;

public class HttpService implements IService
{
    protected static class HttpRejectedExecutionHandler implements RejectedExecutionHandler
    {
        public void rejectedExecution(Runnable runnable, ThreadPoolExecutor executor)
        {
            if (runnable instanceof HttpTask)
            {
                HttpTask task = (HttpTask) runnable;

                task.rejectedHttpTask(504, null, null);
            }
        }
    }

    protected static interface HttpTask extends Runnable
    {
        public void rejectedHttpTask(int code, Map<String, String> headers, byte[] body);
    }

    public static final String description = "com.brotherly.framework.http.HTTP";

    protected static String getContent(HttpEntity entity)
    {
        try
        {
            return new String(getContentBytes(entity), "utf-8");
        }
        catch (Exception e)
        {}

        return "";
    }

    protected static byte[] getContentBytes(HttpEntity entity) throws IOException
    {
        InputStream input = entity.getContent();

        byte[] data = FileSystem.readBytes(input);

        input.close();
        return data;
    }

    protected static InputStream getContentStream(HttpEntity entity) throws IOException
    {
        return new ByteArrayInputStream(getContentBytes(entity));
    }

    protected static byte[] getGzipContentBytes(HttpEntity entity) throws IOException
    {
        InputStream input = entity.getContent();
        GZIPInputStream gzipInput = new GZIPInputStream(new ByteArrayInputStream(FileSystem.readBytes(input)));

        byte[] data = FileSystem.readBytes(gzipInput);

        gzipInput.close();
        input.close();
        return data;
    }

    protected ServiceHost host;

    protected HttpParams params;

    protected SparseArray<StateProvider> providers;

    protected ThreadPoolExecutor threadPool;

    public HttpService(ServiceHost host)
    {
        this.host = host;

        providers = new SparseArray<StateProvider>();

        threadPool = new ThreadPoolExecutor(0, 3, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(300),
                new HttpRejectedExecutionHandler());

        params = new BasicHttpParams();

        HttpConnectionParams.setConnectionTimeout(params, 20000);
        HttpConnectionParams.setSoTimeout(params, 20000);
    }

    protected HttpResponse execute(HttpUriRequest request, LogService log)
    {
        try
        {
            HttpClient client = new DefaultHttpClient(params);

            HttpResponse response = client.execute(request);

            return response;
        }
        catch (Exception e)
        {
            log(log, 5, "Http Service", String.format("exceptoin of execute http: %s", e.toString()));

            e.printStackTrace();
        }

        return null;
    }

    protected String getState()
    {
        JSONParser parser = new JSONParser();
        JSONObject states = new JSONObject();

        for (int i = 0; i < providers.size(); i++)
        {
            int key = providers.keyAt(i);
            StateProvider provider = providers.get(key);
            try
            {
                String name = provider.getName();
                String state = provider.getState();

                if (name == null)
                    continue;

                try
                {
                    states.put(name, parser.parse(state));
                }
                catch (Exception e)
                {
                    states.put(name, state);
                }
            }
            catch (Exception e)
            {}
        }

        return states.toString();
    }

    @Override
    public void init()
    {

    }

    protected void log(LogService log, int level, String name, String message)
    {
        try
        {
            log.log(level, name, message);
        }
        catch (Exception e)
        {}
    }

    protected void parseState(String content)
    {
        JSONObject states = null;

        try
        {
            states = (JSONObject) (new JSONParser()).parse(content);
        }
        catch (Exception e)
        {}

        EventService eventServ = (EventService) host.getService(EventService.description);

        Iterator<Entry<String, Object>> it = states.entrySet().iterator();
        while (it.hasNext())
        {
            Map.Entry<String, Object> entry = it.next();

            String name = entry.getKey().toString();
            String state = entry.getValue().toString();

            String global_event_id = "com.brotherly.framework.state";
            String event_id = String.format("com.brotherly.framework.state.%s", name);

            try
            {
                eventServ.signEvent(event_id, name, state);
                eventServ.signEvent(global_event_id, name, state);
            }
            catch (Exception e)
            {}
        }
    }

    public int registerStateProvider(StateProvider provider)
    {
        providers.put(Integer.valueOf(provider.hashCode()), provider);
        return provider.hashCode();
    }

    public void request(final String url, final String method, final Map<String, Object> headers, final byte[] body,
            final HttpListener listener, boolean wait)
    {
        final LogService log = (LogService) TransactionServiceHost.getInstance().getService(LogService.description);

        Runnable runnable = new HttpTask()
        {
            @Override
            public void rejectedHttpTask(int code, Map<String, String> headers, byte[] body)
            {
                try
                {
                    listener.result(code, headers, body);
                }
                catch (Exception e)
                {}
            }

            @Override
            public void run()
            {
                NetworkWatcher watcher = NetworkWatcher.getInstance();

                HttpRequestBase request = null;

                if (method.toLowerCase(Locale.ENGLISH).equals("get"))
                {
                    request = new HttpGet(url);
                } else if (method.toLowerCase(Locale.ENGLISH).equals("post"))
                {
                    request = new HttpPost(url);

                    ((HttpPost) request).setEntity(new ByteArrayEntity(body));
                } else if (method.toLowerCase(Locale.ENGLISH).equals("head"))
                {
                    request = new HttpHead(url);
                } else if (method.toLowerCase(Locale.ENGLISH).equals("put"))
                {
                    request = new HttpPut(url);

                    if (body != null)
                    {
                        ((HttpPut) request).setEntity(new ByteArrayEntity(body));
                    }
                } else if (method.toLowerCase(Locale.ENGLISH).equals("trace"))
                {
                    request = new HttpTrace(url);
                } else if (method.toLowerCase(Locale.ENGLISH).equals("delete"))
                {
                    request = new HttpDelete(url);
                }

                User user = BaseApplication.getInstance().getUser();
                request.addHeader("x-clm-imei", user.IMEI);
                request.addHeader("x-clm-simei", user.IMSI);
                request.addHeader("x-clm-device", user.Device);
                request.addHeader("x-clm-agent", user.UserAgent);
                request.addHeader("x-clm-version", user.VersionCode + "");
                request.addHeader("Accept-Encoding", "gzip");

                boolean needContentType = true;

                if (headers != null)
                {
                    Iterator<Entry<String, Object>> it = headers.entrySet().iterator();
                    while (it.hasNext())
                    {
                        Map.Entry<String, Object> entry = it.next();

                        String key = entry.getKey().toString();
                        String value = entry.getValue().toString();

                        if (key.toLowerCase(Locale.ENGLISH).equals("content-type"))
                            needContentType = false;

                        request.addHeader(key, value);
                    }
                }

                if (needContentType)
                    request.addHeader("content-type", "application/json;charset=UTF-8");

                request.addHeader("aloca-state", getState());

                log(log, 5, "Http Service", String.format("execute request: %s %s", method, url));

                if (body != null)
                    watcher.reportUpload(body.length);

                HttpResponse response = execute(request, log);
                if (response == null)
                {
                    try
                    {
                        listener.result(500, null, null);
                    }
                    catch (Exception e)
                    {}

                    log(log, 5, "Http Service", String.format("failed request: %s %s", method, url));

                    watcher.reportExceptionTask();

                    return;
                }

                int code = response.getStatusLine().getStatusCode();

                if ((code / 100) >= 4)
                {
                    watcher.reportFailedTask();
                } else
                {
                    watcher.reportCompleteTask();
                }

                log(log, 5, "Http Service", String.format("response %d: %s %s", code, method, url));

                Map<String, String> reponse_headers = new HashMap<String, String>();

                for (Header header : response.getAllHeaders())
                {
                    String key = header.getName();
                    String value = header.getValue();

                    if (key.toLowerCase(Locale.ENGLISH).equals("aloca-state"))
                    {
                        parseState(value);
                    }

                    reponse_headers.put(key, value);
                }

                byte[] data = new byte[0];

                try
                {
                    Header contentEncoding = response.getFirstHeader("Content-Encoding");
                    if (contentEncoding != null && contentEncoding.getValue().toLowerCase(Locale.ENGLISH).equals("gzip"))
                    {
                        data = getGzipContentBytes(response.getEntity());
                    } else
                    {
                        data = getContentBytes(response.getEntity());
                    }

                    watcher.reportDownload(data.length);
                }
                catch (Exception e)
                {}

                try
                {
                    if (code != 200)
                    {
                        String content = new String(data, "utf-8");

                        System.out.println(content);
                    }
                }
                catch (Exception e)
                {}

                try
                {
                    listener.result(code, reponse_headers, data);
                }
                catch (Exception e)
                {}
            }
        };

        log(log, 5, "Http Service", String.format("request: %s %s", method, url));

        if (url.length() == 0)
        {
            throw new RuntimeException();
        }

        if (wait)
        {
            runnable.run();
        } else
        {
            threadPool.execute(runnable);
        }
    }

    public void unregisterStateProvider(int stub)
    {
        providers.remove(Integer.valueOf(stub));
    }
}
